#include <common/define.h>
#include <common/EventBase.h>
#include <expander/Expander.h>
#include <flashpp/Flashpp.h>
#include <folly/logging/xlog.h>
#include <folly/system/ThreadName.h>
#include <model-updater/ModelUpdater.h>
#include <normalizer/Normalizer.h>
#include <pipeline/ActionAggregator.h>

#include <bdd.h>

#ifdef USE_MTBDD
#include <sylvan_obj.hpp>
#endif
#ifdef USE_NANOBDD_PRO
#include <nanobdd/nanobdd.h>
#endif

namespace flashpp {

Flashpp::Flashpp(Network& network) : network_(network) {
  pipeline = Pipeline(100, 100, 100, 100);
#ifdef USE_BDD
  bdd_init(10000000, 10000000);
  bdd_setvarnum(32);
  bdd_disable_reorder();
  bdd_setcacheratio(64);
#endif
#ifdef USE_MTBDD
  lace_start(32, 0);
  //  2^26 nodes is 2048 MB, 2^24 cache is 576 MB
  sylvan::Sylvan::initPackage(1LL << 28, 1LL << 30, 1LL << 24, 1LL << 28);
  sylvan::Sylvan::initBdd();
  for (int i = 0; i < 32; i++) {
    HeaderSpace::vars.push_back(sylvan::Bdd::bddVar(i));
  }
#endif
#ifdef USE_NANOBDD
  // auto nbdd = nanobdd::Nanobdd(10000000);
  HeaderSpace::nbdd.createVars(32);
#endif
#ifdef USE_NANOBDD_PRO
nanobdd::init(1000000, 10000000, 32);
#endif
}

Flashpp::~Flashpp() {}

/**
 * Start an EventBase in a thread, maintain order of thread creation and
 * returns raw pointer of Derived class.
 */
template <typename T>
T*
Flashpp::startEventBase(
    std::vector<std::thread>& allThreads,
    std::vector<std::unique_ptr<EventBase>>& orderedEvbs,
    const std::string& name,
    std::unique_ptr<T> evbT) {
  CHECK(evbT);
  auto t = evbT.get();
  auto evb =
      std::unique_ptr<EventBase>(reinterpret_cast<EventBase*>(evbT.release()));
  evb->setEvbName(name);

  // Start a thread
  allThreads.emplace_back(std::thread([evb = evb.get(), name]() noexcept {
    XLOG(INFO) << "Starting " << name << " thread ...";
    folly::setThreadName(fmt::format("flashpp-{}", name));
    evb->run();
    XLOG(INFO) << name << " thread got stopped.";
  }));
  evb->waitUntilRunning();

  // Emplace evb into ordered list of evbs. So that we can destroy
  // them in revserse order of their creation.
  orderedEvbs.emplace_back(std::move(evb));

  return t;
}

void
Flashpp::push(std::shared_ptr<DataPlaneUpdate> dpu) {
  pipeline.blockingWrite(std::move(dpu));
}

void
Flashpp::run() {
  auto normalizer = startEventBase(
      allThreads,
      orderedEvbs,
      "normalizer",
      std::make_unique<Normalizer>(pipeline));
  auto expander = startEventBase(
      allThreads,
      orderedEvbs,
      "expander",
      std::make_unique<Expander>(pipeline, network_));
  auto actionAggregator = startEventBase(
      allThreads,
      orderedEvbs,
      "actionAggregator",
      std::make_unique<ActionAggregator>(pipeline));
  auto modelUpdater = startEventBase(
      allThreads,
      orderedEvbs,
      "modelUpdater",
      std::make_unique<ModelUpdater>(pipeline, network_));
}

} // namespace flashpp